# 手写qtip插件

qtip就是鼠标移入显示一个提示框的功能,名字来源于jq的qtip插件。

由于项目不需要jq,所以这里自己动手实现一个。

# 实现

因为是在G6.js下开发,所以直接用他们的修改css和创建dom方法了。

主要逻辑就是通过对传入的dom元素进行遍历,找出所有包含qtip(名字可自定义)的元素,增加鼠标事件。

如果鼠标的x,y加上偏移值没超出边界,就使用该值。

超出了则用鼠标的x,y减去该显示元素的宽高。

/**
 * qTip
 * 鼠标hover的弹窗提示插件
 */

import modifyCSS from '@antv/dom-util/lib/modify-css';
import createDOM from '@antv/dom-util/lib/create-dom';
import mergeWith from 'lodash/mergeWith';
import { qTipConfig } from '@/config/plugins';
import '@/style/qTip.less';

class QTip {
  constructor(config) {
    this._config = mergeWith(
      {},
      qTipConfig,
      config
    );

    this.initBindEvent();
    this.initQTip();
  }

  initBindEvent() {
    const _this = this;
    this.bindEventMouseEnter = function(e) {
      _this.eventMouseEnter(e);
    };
    this.bindEventMouseLeave = function(e) {
      _this.eventMouseLeave(e);
    };
    this.bindEventMouseMove = function(e) {
      _this.eventMouseMove(e);
    };
  }

  initQTip() {
    this.initListener();
  }

  /**
   * 对传入dom遍历监听 qTip:xx 属性
   */
  initListener() {
    const { target, qTipSymbol } = this._config;
    if (!target) {
      window.graphConsole.info(window._('传入参数错误:'), window._('target不存在'));
      return false;
    }
    const attributeName = `[${qTipSymbol}]`;
    this.ListenerChildren = Array.from(target.querySelectorAll(attributeName));

    this.ListenerChildren.forEach(child => {
      child.addEventListener('mouseenter', this.bindEventMouseEnter);
      child.addEventListener('mouseleave', this.bindEventMouseLeave);
      child.addEventListener('mousemove', this.bindEventMouseMove);
    });
  }

  eventMouseEnter(e) {
    this.showTip(e);
  }

  eventMouseLeave() {
    modifyCSS(this.dom, {
      visibility: 'hidden'
    });
  }

  eventMouseMove(e) {
    this.showTip(e);
  }

  showTip(e) {
    const { dom, graph, offsetX, offsetY, qTipSymbol, style } = this._config;
    const { target, clientX, clientY } = e;
    const graphWidth = graph.get('width');
    const graphHeight = graph.get('height');

    let tip;
    let targetEle = target;
    while (!tip) {
      // 事件是子元素触发时,向上找
      tip = targetEle.getAttribute(qTipSymbol);
      targetEle = targetEle.parentNode;
    }

    // 减少dom操作
    if (!this.dom) {
      this.dom = createDOM(dom);
      graph.get('container').appendChild(this.dom);
    }

    this.dom.innerHTML = tip;
    const { width, height } = this.dom.getBoundingClientRect();
    let x = clientX + offsetX;
    let y = clientY + offsetY;
    if (x + width > graphWidth) {
      x = x - width - offsetX;
    }
    if (y + height > graphHeight) {
      y = y - height - offsetY;
    }

    modifyCSS(this.dom, {
      left: x + 'px',
      top: y + 'px',
      visibility: 'visible',
      ...style
    });
  }

  removeListener() {
    this.ListenerChildren.forEach(child => {
      child.removeEventListener('mouseenter', this.bindEventMouseEnter);
      child.removeEventListener('mouseleave', this.bindEventMouseLeave);
      child.removeEventListener('mousemove', this.bindEventMouseMove);
    });
  }

  destroy() {
    this.removeListener();
  }
}

export default QTip;